/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.naming;

import it.free.i.virtual.machine.tools.PropertyLoader;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;

import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/** 
 * Naming provider accessor
 * This is the jndi naming subsystem gateway
 * @author stefano
 */
public class CachedNamingProviderProxy extends Hashtable<String, Object>{

	private static final long serialVersionUID = -6110413510212716809L;

	private static CachedNamingProviderProxy NAMING_PROVIDER_PROXY_INSTANCE = null;
	
	private static final String JAVA_NAMING_FACTORY_INITIAL = "java.naming.factory.initial";
	private static final String JAVA_NAMING_PROVIDER_URL = "java.naming.provider.url";
	private static final String JAVA_NAMING_SECURITY_PRINCIPAL = "java.naming.security.principal";
	private static final String JAVA_NAMING_SECURITY_AUTHENTICATION = "java.naming.security.authentication";
	private static final String JAVA_NAMING_SECURITY_CREDENTIALS = "java.naming.security.credentials";
	
	private static Hashtable<String, String> namingContextEnvironment = null;
	
	/**
	 * Singleton makes this private
	 */
	private CachedNamingProviderProxy() {
		super();
	}
	
	public static final CachedNamingProviderProxy getInstance(){
		
		if(NAMING_PROVIDER_PROXY_INSTANCE == null){
		   synchronized(CachedNamingProviderProxy.class) {
			   if(NAMING_PROVIDER_PROXY_INSTANCE == null){
				  NAMING_PROVIDER_PROXY_INSTANCE = new CachedNamingProviderProxy();
			   }
		   }
		}
		return NAMING_PROVIDER_PROXY_INSTANCE;
	}
	
	/**
	 * Performe setup activity, checking jndi file consistency
	 * @param jndiConfigurationFile is the file  keeping the jndi properties needed to start up the naming proxy
	 * @throws NamingException
	 */
	public void setup(String jndiConfigurationFile) throws NamingException{
		initializeNamingContextEnvironment(jndiConfigurationFile);
	}
	
	/**
	 * Perform tasks needed to initialize the naming context environment for this proxy
	 */
	private void initializeNamingContextEnvironment(String jndiPropertiesFile) throws NamingException {
		
		synchronized (CachedNamingProviderProxy.class) {
			try{
				Properties properties = PropertyLoader.loadProperties(jndiPropertiesFile);
		
				namingContextEnvironment = new Hashtable<String, String>();
			
				namingContextEnvironment.put(JAVA_NAMING_FACTORY_INITIAL, properties.getProperty(JAVA_NAMING_FACTORY_INITIAL));
				namingContextEnvironment.put(JAVA_NAMING_PROVIDER_URL, properties.getProperty(JAVA_NAMING_PROVIDER_URL));
				namingContextEnvironment.put(JAVA_NAMING_SECURITY_PRINCIPAL, properties.getProperty(JAVA_NAMING_SECURITY_PRINCIPAL));
				namingContextEnvironment.put(JAVA_NAMING_SECURITY_AUTHENTICATION, properties.getProperty(JAVA_NAMING_SECURITY_AUTHENTICATION));
				namingContextEnvironment.put(JAVA_NAMING_SECURITY_CREDENTIALS, properties.getProperty(JAVA_NAMING_SECURITY_CREDENTIALS));
			
			}catch(Exception exc){
				   throw (new NamingException(exc.getMessage()));
			}
		}
	}

	/**
	 * Create a directory context
	 * @return a directory context created
	 * @throws NamingException
	 */
	private static final DirContext createDirectoryContext() throws NamingException{
	
		DirContext directoryContext = new InitialDirContext(namingContextEnvironment);
		
		return directoryContext;
	}
	
	/**
	 * Check if the server vpnfiguration can actually be used
	 * @throws NamingException
	 */
	public void checkNamingServer() throws NamingException {
		
		DirContext dirContext = null;
		try{
			dirContext = createDirectoryContext();
		}finally{
			
			if(dirContext != null){
			   dirContext.close();
			}
		}
	}
	
	/**
	 * Lookup an on bject in a jndi repository
	 * @param name to lookup
	 * @return the object found
	 * @throws NamingException
	 */
	public Object lookup(String name) throws NamingException {
	
		
		Object objectFound = null;
		
		if(this.containsKey(name)){
		   objectFound = this.get(name);
		}else{
			  DirContext dirContext = null;
		      try{
			      dirContext = createDirectoryContext();
			      objectFound = dirContext.lookup(name);
		      }finally{
			           if(dirContext!= null){
			    	      dirContext.close();
			           }
		      }
		      synchronized (CachedNamingProviderProxy.class) {
		    	  if(!this.containsKey(name)){
		    		  this.put(name, objectFound); 
		    	  }else{
		    		    objectFound = this.get(name);
		    	  }
		    	  
			}
		}
		return objectFound;
	}
	
	/**
	 * Create a new binding in the jndi repository, if it aleady exists, it will be overwritten
	 * @param jndiPathToBind is the jndi name to store
	 * @param objectToRebind is the object to store
	 * @throws NamingException
	 */
	public synchronized void rebind(String jndiPathToBind, Object objectToRebind) throws NamingException {
		
		DirContext dirContext = null;
		try{
			dirContext = createDirectoryContext();
			dirContext.rebind(jndiPathToBind, objectToRebind);
		}finally{
			     if(dirContext!= null){
			    	dirContext.close();
			     }
		}
		this.put(jndiPathToBind, objectToRebind);
	}
	
	/**
	 * Lookup configuration by name
	 * @param name is the name of the subcontext to explore to look for a config
	 * properties bundle
	 * @return configuration properties 
	 * @throws NamingException 
	 */
	public Properties lookUpConfiguration(String name) throws NamingException{
		
		Properties properties = null;
		
		try{
		    Object lookedUpObject = this.lookup(name);
		    Reference reference = (Reference) lookedUpObject;
		    Enumeration<RefAddr> names = reference.getAll(); 
		    
		    properties = new Properties();
		    
		    String attributeName = null;
		    String attributeValue = null;
		    
		    RefAddr refAddr = null;
		    
		    while(names.hasMoreElements()){
		    	  
		    	  refAddr = names.nextElement();
		    	  attributeName = refAddr.getType();
		    	  attributeValue = (String) refAddr.getContent();
		    
		    	  properties.put(attributeName, attributeValue);
		    }
		    
		}catch(ClassCastException ccExc){
			   throw (new NamingException(ccExc.getMessage()));
		}
		
		return properties;
	}
    
}
